package freighttmpl

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"strings"

	"mall/common/globalkey"
	"mall/common/moneyx"
	enum "mall/service/backend/common/enum/setting"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/i18n"
	"mall/service/backend/common/utils/pointy"
	"mall/service/setting/model"
	"mall/service/setting/rpc/internal/svc"
	"mall/service/setting/rpc/internal/utils/errorhandler"
	"mall/service/setting/rpc/types/setting"

	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type GetFreightLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewGetFreightLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFreightLogic {
	return &GetFreightLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *GetFreightLogic) GetFreight(in *setting.FreightReq) (*setting.FreightResp, error) {
	logrus.Info(fmt.Sprintf("GetFreight FreightReq: %+v", in))
	// GetFreight FreightReq: Regions:"140000,140500,140525" List:{Number:12 Amount:"116" Weight:"0.03"}

	if in.Regions == nil || len(in.List) == 0 {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	var regionCodes string
	if regions := strings.Split(strings.TrimSpace(*in.Regions), ","); len(regions) == 0 {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	} else {
		regionCodes = fmt.Sprintf(",%s,", strings.Join(regions, ","))
	}

	var tmplIds []any
	for _, info := range in.List {
		if info.TemplateId != nil && *info.TemplateId > 0 {
			tmplIds = append(tmplIds, *info.TemplateId)
		}
	}
	if len(tmplIds) == 0 {
		tmplIds = append(tmplIds, globalkey.DefaultFreightTemplate)
	}

	filterMap := make(map[string]map[string][]any)
	filterMap[model.SysFreightTemplateColumns.Status] = map[string][]any{
		"equalTo": []any{globalkey.StatusEnabled},
	}
	filterMap[model.SysFreightTemplateColumns.ID] = map[string][]any{
		"in": tmplIds,
	}
	list, _, err := l.svcCtx.FreightTemplate.GetList(l.ctx, model.M{}, -1, filterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	resp := &setting.FreightResp{}
	if len(list) == 0 {
		for _, info := range in.List {
			resp.List = append(resp.List, &setting.FreightInfo{
				TemplateId: pointy.GetPointer(globalkey.DefaultFreightTemplate),
				SkuId:      info.SkuId,
				Number:     info.Number,
				Amount:     info.Amount,
				Weight:     info.Weight,
				Volumn:     info.Volumn,
				Name:       pointy.GetPointer(globalkey.DefaultFreightTemplateName),
				Freight:    pointy.GetPointer(globalkey.DefaultFreight),
			})
		}

		return resp, nil
	}

	idTmplMap := make(map[uint64]*model.SysFreightTemplate)
	idTmplRuleMap := make(map[uint64][]*model.SysFreightTemplateRule)
	idTmplRestrictionMap := make(map[uint64][]*model.SysFreightTemplateRestriction)
	var ids []any
	for _, v := range list {
		ids = append(ids, v.ID)
		idTmplMap[v.ID] = v
	}

	resFilterMap := make(map[string]map[string][]any)
	resFilterMap[model.SysFreightTemplateRestrictionColumns.Status] = map[string][]any{
		"equalTo": []any{globalkey.StatusEnabled},
	}
	resFilterMap[model.SysFreightTemplateRestrictionColumns.TemplateID] = map[string][]any{
		"in": ids,
	}
	resList, _, err := l.svcCtx.FreightTemplate.GetRestrictionList(l.ctx, model.M{
		"orderBy": model.SysFreightTemplateRestrictionColumns.Sort + " DESC",
	}, -1, resFilterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}
	for _, v := range resList {
		if v.Regions.Valid {
			var regions []*model.TreeNode
			if err := json.Unmarshal(v.Regions.JSON, &regions); err != nil {
				logx.Errorw("GetFreight Restrictions.Regions json.Unmarshal err", logx.Field("detail", err.Error()))
			} else {
				if len(regions) == 0 {
					idTmplRestrictionMap[v.TemplateID] = append(idTmplRestrictionMap[v.TemplateID], v)
					continue
				}

				for _, node := range regions {
					if inScope := strings.Index(regionCodes, node.Code); inScope >= 0 {
						idTmplRestrictionMap[v.TemplateID] = append(idTmplRestrictionMap[v.TemplateID], v)
						break
					}
				}
			}
		}
	}

	ruleFilterMap := make(map[string]map[string][]any)
	ruleFilterMap[model.SysFreightTemplateRuleColumns.Status] = map[string][]any{
		"equalTo": []any{globalkey.StatusEnabled},
	}
	ruleFilterMap[model.SysFreightTemplateRuleColumns.TemplateID] = map[string][]any{
		"in": ids,
	}
	ruleList, _, err := l.svcCtx.FreightTemplate.GetRuleList(l.ctx, model.M{
		"orderBy": model.SysFreightTemplateRuleColumns.Sort + " DESC",
	}, -1, ruleFilterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	for _, v := range ruleList {
		if v.Regions.Valid {
			var regions []*model.TreeNode
			if err := json.Unmarshal(v.Regions.JSON, &regions); err != nil {
				logx.Errorw("GetFreight Rules.Regions json.Unmarshal err", logx.Field("detail", err.Error()))
			} else {
				if len(regions) == 0 {
					idTmplRuleMap[v.TemplateID] = append(idTmplRuleMap[v.TemplateID], v)
					continue
				}

				for _, node := range regions {
					if inScope := strings.Index(regionCodes, node.Code); inScope >= 0 {
						idTmplRuleMap[v.TemplateID] = append(idTmplRuleMap[v.TemplateID], v)
						break
					}
				}
			}
		}
	}

	for _, info := range in.List {
		var tmplId uint64 = globalkey.DefaultFreightTemplate
		if info.TemplateId != nil && *info.TemplateId > 0 {
			tmplId = *info.TemplateId
		}
		var number uint64
		if info.Number != nil && *info.Number > 0 {
			number = *info.Number
		}
		var amount uint64
		if info.Amount != nil && len(*info.Amount) > 0 {
			if val, err := moneyx.ParseAmount(*info.Amount); err == nil {
				amount = uint64(val)
			} else {
				logx.Errorw("GetFreight ParseAmount err", logx.Field("detail", err.Error()))
			}
		}
		var weight uint64
		if info.Weight != nil && len(*info.Weight) > 0 {
			if val, err := moneyx.ParseWeight(*info.Weight); err == nil {
				weight = uint64(val)
			} else {
				logx.Errorw("GetFreight ParseWeight err", logx.Field("detail", err.Error()))
			}
		}
		var volume uint64
		if info.Volumn != nil && len(*info.Volumn) > 0 {
			if val, err := moneyx.ParseVolume(*info.Volumn); err == nil {
				volume = uint64(val)
			} else {
				logx.Errorw("GetFreight ParseVolume err", logx.Field("detail", err.Error()))
			}
		}

		newInfo := &setting.FreightInfo{
			TemplateId: pointy.GetPointer(globalkey.DefaultFreightTemplate),
			SkuId:      info.SkuId,
			Number:     info.Number,
			Amount:     info.Amount,
			Weight:     info.Weight,
			Volumn:     info.Volumn,
			Name:       pointy.GetPointer(globalkey.DefaultFreightTemplateName),
			Freight:    pointy.GetPointer(globalkey.DefaultFreight),
		}
		if tmpl, ok := idTmplMap[tmplId]; ok {
			newInfo.TemplateId = pointy.GetPointer(tmplId)
			newInfo.Name = &tmpl.Name
			newInfo.Desc = &tmpl.Remark

			var free bool
			if restrictions, exist := idTmplRestrictionMap[tmplId]; exist {
				for _, restriction := range restrictions {
					switch restriction.Mode {
					case enum.FreightTemplateRestrictionAmount: // 按金额
						if amount >= restriction.Threshhold {
							free = true
							continue
						}
					case enum.FreightTemplateRestrictionNumber: // 按件数
						if number >= restriction.Threshhold {
							free = true
							continue
						}
					}
				}
			}
			if !free {
				if rules, exist := idTmplRuleMap[tmplId]; exist {
					var freight float64
					var breakFlag bool
					for _, rule := range rules {
						if rule.ChargeMode == tmpl.ChargeMode {
							switch rule.ChargeMode {
							case enum.FreightTemplateRuleNumber: // 按件数
								freight = float64(rule.FirstPrice) +
									math.Ceil(float64(number - rule.FirstUnit) / float64(rule.ContinueUnit)) * float64(rule.ContinuePrice)
								breakFlag = true
							case enum.FreightTemplateRuleWeight: // 按重量
								freight = float64(rule.FirstPrice) +
									math.Ceil(float64(weight - rule.FirstUnit) / float64(rule.ContinueUnit)) * float64(rule.ContinuePrice)
								breakFlag = true
							case enum.FreightTemplateRuleVolume: // 按体积
								freight = float64(rule.FirstPrice) +
									math.Ceil(float64(volume - rule.FirstUnit) / float64(rule.ContinueUnit)) * float64(rule.ContinuePrice)
								breakFlag = true
							}
						}
						if breakFlag {
							break
						}
					}
					if int64(freight) > 0 {
						newInfo.Freight = pointy.GetPointer(moneyx.FormatAmount(int64(freight)))
					}
				}
			} else {
				newInfo.Freight = pointy.GetPointer("0")
			}
		}
		resp.List = append(resp.List, newInfo)
	}

	return resp, nil
}
